from django.db.models import Q
from django.http import JsonResponse, HttpResponse
from rest_framework import generics
from rest_framework.views import APIView
from tablib import Databook
from datetime import datetime
import sys
from django.utils.translation import gettext_lazy as _
from django.contrib.auth import get_user_model

from audit.models import AuditManagement
from lib.rest_api.perms import HasDjangoPermission
from django_lispy_filter import LispyFilterParser, get_models_schema

from .. import models
from .. import admin
from . import serializers
from .lib import ProjectSubListView, ProjectSubWithAuditListView

User = get_user_model()

MODEL_RESOURCE_MAP = {
    'Project': admin.ProjectResources,
    'Plan': admin.PlanResources,
    'InitiationData': admin.InitiationDataResources,
    'EthicalReview': admin.EthicalReviewResources,
    'BeforeImplement': admin.BeforeImplementResources,
    'HumanGeneticResource': admin.HumanGeneticResourceResources,
    'HGRInternationalCooperation': admin.HGRInternationalCooperationResources,
    'HGRCollection': admin.HGRCollectionResources,
    'HGRStorage': admin.HGRStorageResources,
    'HGRMaterialExport': admin.HGRMaterialExportResources,
    'HGRDataFiling': admin.HGRDataFilingResources,
    'Contract': admin.ContractResources,
    'Material': admin.MaterialResources,
    'Beginning': admin.BeginningResources,
    'Implement': admin.ImplementResources,
    'QualityControl': admin.QualityControlResources,
    'Finish': admin.FinishResources,
    'Examination': admin.ExaminationResources,
}

VERBOSE_NAME_RESOURCE_MAP = {
    getattr(sys.modules['project.models'], module_name)._meta.verbose_name: resource
    for module_name, resource in MODEL_RESOURCE_MAP.items()
}


class ProjectSchemaView(APIView):
    def get(self, request, format=None):
        schema = get_models_schema([
            models.Project, models.Plan, models.InitiationData,
            models.EthicalReview, models.BeforeImplement,
            models.HumanGeneticResource, models.HGRInternationalCooperation,
            models.HGRCollection, models.HGRStorage, models.HGRMaterialExport,
            models.HGRDataFiling, models.Contract, models.Material, models.Beginning,
            models.Implement, models.QualityControl, models.Finish, models.Examination,
            AuditManagement, User
        ])
        return JsonResponse(schema)


class ProjectExportView(APIView):
    """Export project data to XLSX with multiple sheets"""
    
    def get_permissions(self):
        return [HasDjangoPermission(["user.can_manage_projects"])]

    def post(self, request, format=None):
        project_ids = request.data.get('ids', None)
        sample = request.data.get("sample", None) == True
        export_models = request.data.get('models', [])
        if len(export_models) == 0:
            return JsonResponse({'error': 'At least specify one model to export!'}, status=400)
        if (sample and project_ids is not None):
            return JsonResponse({'error': 'Cannot specify both `sample` and `project_ids`.'})
        
        databook = Databook()
        for model in export_models:
            resource_class = MODEL_RESOURCE_MAP.get(model)
            if not resource_class:
                return JsonResponse({'error': f'Invalid model {model}'}, status=400)
            model_class = resource_class.Meta.model
            if project_ids is not None:
                if model_class == models.Project:
                    queryset = model_class.objects.filter(id__in=project_ids)
                elif model_class in (
                    models.HGRInternationalCooperation, models.HGRCollection,
                    models.HGRStorage, models.HGRMaterialExport, models.HGRDataFiling,
                ):
                    queryset = model_class.objects.filter(HGR__project__id__in=project_ids)
                else:
                    queryset = model_class.objects.filter(project__id__in=project_ids)
            else:
                if sample:
                    queryset = model_class.objects.all()[:]
                else:
                    queryset = model_class.objects.all()
            resource = resource_class()
            resource_data = resource.export(queryset)
            resource_data.title = str(model_class._meta.verbose_name)
            databook.add_sheet(resource_data)

        xlsx_data = databook.export('xlsx')
        
        # with open('export.xlsx', 'wb') as f:
        #     f.write(xlsx_data)
        
        response = HttpResponse(
            xlsx_data,
            content_type='application/vnd.ms-excel',
            # NOTE our user is in the same time zone with our server
            headers={
                'Content-Disposition':
                ("attachment; sample.xlsx"
                 if sample else
                 f'attachment; filename="export-{datetime.now().date()}.xlsx"')
            }
            
        )
        return response

class ProjectDataImportView(APIView):
    def get_permissions(self):
        return [HasDjangoPermission(["user.can_manage_projects"])]

    def post(self, request, format=None):
        try: 
            dry_run = request.POST.get("dry_run") == "true"
            uploaded_file = request.FILES.get('file')
            if not uploaded_file:
                return JsonResponse({'error': 'No file uploaded'}, status=400)
            import_data = Databook().load(uploaded_file, format='xlsx')
            sheets = import_data.sheets()
            result = []
            has_error = False
            for sheet in sheets:
                model_verbose_name = sheet.title
                resource_class = VERBOSE_NAME_RESOURCE_MAP.get(model_verbose_name)
                if not resource_class:
                    return JsonResponse(
                        {'error': _(f"Model {model_verbose_name} is not Project related models.")},
                        status=400
                    )
                resource = resource_class()
                import_result = resource.import_data(sheet, dry_run=True)
                sheet_preview = {
                    'model': model_verbose_name,
                    'total_rows': import_result.total_rows,
                    'diff_headers': import_result.diff_headers,
                    'errors': [],
                    'diffs': []
                }

                row_index = -1
                for row in import_result.rows:
                    row_index += 1
                    row_data = {
                        'row_number': row_index,
                        'errors': list(map(lambda e: str(e.error), row.errors)),
                        'validation_error': (
                            row.validation_error.message_dict
                            if row.validation_error else
                            None
                        ),
                        'diff': row.diff
                    }

                    if row.errors or row.validation_error:
                        sheet_preview['errors'].append(row_data)
                        has_error = True
                    else:
                        sheet_preview['diffs'].append(row_data)

                result.append(sheet_preview)

            if dry_run or has_error:
                return JsonResponse({
                    'results': result,
                }, status = 400 if has_error and not dry_run else 200);
            else:
                return JsonResponse({
                    'message': "Completed!"
                })
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=400)



class ProjectIdsFilterView(APIView):
    filter = LispyFilterParser()
    
    def post(self, request, formst=None):
        query = request.data['query']
        f = self.filter.parseJson(query)
        project_ids = list(models.Project.objects.filter(f).values_list("id", flat=True))
        return JsonResponse({'ids': project_ids})


class ProjectListView(generics.ListAPIView):
    # `select_related` since user serializer fetches `username` field from user
    serializer_class = serializers.ProjectListSerializer

    
    def get_queryset(self):
        user = self.request.user
        base_qs = models.Project.objects.select_related('creator')

        if self.request.query_params.get("all") != "true":
            base_qs = base_qs.filter(
                Q(creator=user)
                | Q(agent_users=user)
            ).distinct()

        raw_ids = self.request.query_params.get("ids")
        if raw_ids is not None: # raw_ids can be ''
            if raw_ids == '':
                ids = []
            else:
                ids = [int(ri) for ri in raw_ids.split(",")]
            base_qs = base_qs.filter(id__in=ids)

        # Get all projects first
        projects = list(base_qs)
        
        if projects:
            # Get all project tags in one query
            project_tags = [f"project-{proj.id}" for proj in projects]
            
            ams = (AuditManagement.objects
                   .select_related('content_type')
                   .prefetch_related('tags')
                   .filter(tags__name__in=project_tags)
                   .distinct())

            pid_am_map = {}
            for am in ams:
                tags = am.tags.all()
                tag = next(filter(lambda t: t.name.startswith('project-'), tags))
                project_id = int(tag.name.split('-')[1])
                pid_am_map.setdefault(project_id, set()).add(am)
                
            for project in projects:
                project._audit_managements = pid_am_map[project.id]

        return projects

    def get_permissions(self):
        list_all = self.request.query_params.get("all", None)
        if list_all == "true":
            return [HasDjangoPermission(["user.can_manage_projects"])]
        else:
            return []

class PlanListView(ProjectSubWithAuditListView):
    queryset = models.Plan.objects.select_related("project").all()
    serializer_class = serializers.PlanListSerializer

class ExaminationListView(ProjectSubListView):
    queryset = models.Examination.objects.select_related("project").all()
    serializer_class = serializers.ExaminationListSerializer

class InitiationDataListView(ProjectSubWithAuditListView):
    queryset = models.InitiationData.objects.select_related("project").all()
    serializer_class = serializers.InitiationDataListSerializer

class EthicalReviewListView(ProjectSubListView):
    queryset = models.EthicalReview.objects.select_related("project").all()
    serializer_class = serializers.EthicalReviewSerializer

class HumanGeneticResourceListView(ProjectSubListView):
    queryset = models.HumanGeneticResource.objects.select_related("project").all()
    serializer_class = serializers.HumanGeneticReousceListSerializer


class ContractListView(ProjectSubListView):
    queryset = models.Contract.objects.select_related("project").all()
    serializer_class = serializers.ContractListSerializer

class MaterialListView(ProjectSubListView):
    queryset = models.Material.objects.select_related("project").all()
    serializer_class = serializers.MaterialListSerializer


class QualityControlListView(ProjectSubListView):
    queryset = models.QualityControl.objects.select_related("project").all()
    serializer_class = serializers.QualityControlListSerializer
